import { useReducer, useEffect, useRef, useCallback } from 'react';
import { runSaga, stdChannel } from 'redux-saga';
import { take, call, effectTypes } from 'redux-saga/effects';

function* selectAsyncSaga(selector, args) {
  const { state } = yield take('REACT_STATE_READY');
  return selector(state, ...args);
}

export default function useReducerAndSaga(reducer, state0, saga, sagaOptions) {
  const [state, reactDispatch] = useReducer(reducer, state0);
  const sagaEnv = useRef({ state: state0, pendingActions: [] });

  const dispatch = useCallback(
    action => {
      reactDispatch(action);
      sagaEnv.current.pendingActions.push(action);
      sagaEnv.current.state = state;
      const pendingActions = sagaEnv.current.pendingActions;
      if (pendingActions.length > 0) {
        sagaEnv.current.pendingActions = [];
        pendingActions.forEach(action => sagaEnv.current.channel.put(action));
        sagaEnv.current.channel.put({ type: 'REACT_STATE_READY', state });
      }
    },
    [state],
  );

  useEffect(() => {
    sagaEnv.current.channel = stdChannel();
    const task = runSaga(
      {
        ...sagaOptions,
        channel: sagaEnv.current.channel,
        dispatch,
        getState: () => {
          /* overrided by effectMiddlewares below */
        },
        effectMiddlewares: [
          runEffect => {
            return effect => {
              if (effect.type === effectTypes.SELECT) {
                return runEffect(
                  call(
                    selectAsyncSaga,
                    effect.payload.selector,
                    effect.payload.args,
                  ),
                );
              }
              return runEffect(effect);
            };
          },
        ],
      },
      saga,
    );
    return () => task.cancel();
  }, [dispatch, saga, sagaOptions]);

  return [state, dispatch];
}
